﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Windows.Forms;

namespace NExplorer.Controls
{
	internal class DirectoryTreeNode : TreeNode, IDisposable
	{
		private bool						_hasExpanded;
		private LowerCaseString				_lowerCaseText;
		private readonly DirectoryInfo		_directoryInfo;
        private readonly FileSystemWatcher	_fsWatcher;

		public DirectoryTreeNode(DirectoryInfo directoryInfo) :
			this(directoryInfo, directoryInfo.Name)
		{}

		public DirectoryTreeNode(DirectoryInfo directoryInfo, string text)
			: base(text)
		{
			_directoryInfo = directoryInfo;
			_lowerCaseText = new LowerCaseString(directoryInfo.Name);

			try
			{
				_fsWatcher = new FileSystemWatcher(directoryInfo.FullName);
				_fsWatcher.IncludeSubdirectories = false;
				_fsWatcher.NotifyFilter = NotifyFilters.DirectoryName;
				_fsWatcher.Deleted += HandleFileSystemChange;
				_fsWatcher.Renamed += HandleFileSystemChange;
				_fsWatcher.Created += HandleFileSystemChange;
				_fsWatcher.EnableRaisingEvents = true;
			}
			catch 
			{
				// Not vital.
			}
		}

		private void HandleFileSystemChange(object sender, FileSystemEventArgs e)
		{
			if (TreeView.InvokeRequired)
			{
				TreeView.Invoke(new FileSystemEventHandler(HandleFileSystemChange), null, e);
			}
			else 
			{
				if (e.ChangeType == WatcherChangeTypes.Renamed)
				{
					string oldName = ((RenamedEventArgs)e).OldName;
					DirectoryTreeNode treeNode = GetChildNode(new LowerCaseString(oldName), false);

					if (treeNode != null)
					{
						treeNode.Text = e.Name;
						treeNode._lowerCaseText = new LowerCaseString(e.Name);
					}
				}
				else if (e.ChangeType == WatcherChangeTypes.Deleted)
				{
					DirectoryTreeNode node = GetChildNode(new LowerCaseString(e.Name), false);

					if (node != null)
					{
						node.Dispose();
						node.Remove();
					}
				}
				else if (e.ChangeType == WatcherChangeTypes.Created)
				{
					DirectoryTreeNode treeNode = new DirectoryTreeNode(new DirectoryInfo(e.FullPath));
					AddNodes(treeNode.ToArray());
				}
			}
		}

		public void Initialize()
		{
			Icon icon = Utilities.GetIcon(_directoryInfo.FullName, IconSize.Small);

			if (icon != null)
			{
				TreeView.ImageList.Images.Add(icon);
				ImageIndex = SelectedImageIndex = TreeView.ImageList.Images.Count - 1;
			}

			DirectoryInfo[] directories;

			try
			{
				directories = _directoryInfo.GetDirectories();
			}
			catch (UnauthorizedAccessException)
			{
				directories = new DirectoryInfo[0];
			}
			catch (IOException)
			{
				if (Path.GetPathRoot(_directoryInfo.FullName) == _directoryInfo.FullName && !new DriveInfo(_directoryInfo.FullName).IsReady)
				{
					// If it's a drive, we can let it slide. Otherwise, throw.
					directories = new DirectoryInfo[0];
				}
				else
				{
					throw;
				}
			}

			if (directories.Length > 0)
			{
				Nodes.Add("Dummy");
			}
		}

		public void PrepareForExpand()
		{
			if (!_hasExpanded)
			{
				Populate();
				_hasExpanded = true;
			}
		}

		public DirectoryTreeNode GetChildNode(LowerCaseString text, bool autoPopulate)
		{
			if (autoPopulate)
			{
				PrepareForExpand();
			}

			DirectoryTreeNode foundNode = null;

			foreach (TreeNode childNode in Nodes)
			{
				DirectoryTreeNode childDirectoryNode = childNode as DirectoryTreeNode;
				
				if (childDirectoryNode != null && childDirectoryNode._lowerCaseText == text)
				{
					foundNode = childDirectoryNode;
					break;
				}
			}

			return foundNode;
		}

		private void Populate()
		{
			Nodes.Clear();

			TreeView.BeginUpdate();

			List<DirectoryTreeNode> nodes = new List<DirectoryTreeNode>();

			// Add all subdirectories
			try
			{
				foreach (DirectoryInfo dirInfo in _directoryInfo.GetDirectories())
				{
					if (Utilities.CanShowFileSystemItem(dirInfo))
					{
						nodes.Add(new DirectoryTreeNode(dirInfo));
					} 
				}
			}
			catch (UnauthorizedAccessException)
			{}

			AddNodes(nodes);

			TreeView.EndUpdate();
		}

		private void AddNodes(IEnumerable<DirectoryTreeNode> nodes)
		{
			foreach (DirectoryTreeNode node in nodes)
			{
				Nodes.Add(node);
				node.Initialize();
			}			
		}

		public DirectoryInfo DirectoryInfo
		{
			[DebuggerStepThrough]
			get { return _directoryInfo; }
		}

		public void Dispose()
		{
			_fsWatcher.EnableRaisingEvents = false;
			_fsWatcher.Dispose();
		}
	}
}